using System;
using System.IO;
using System.Text;
using AutoBuild.Core.Configuration.Runner;
using AutoBuild.Core.Framework;
using NDesk.Options;
using AutoBuild.Core;
using System.Linq;

namespace AutoBuild.Console.CommandLine
{

    //--------------------------------------------------------- 
    public class CommandLineParser 
    {
        //---- Members ----------------------------------------------------- 
        readonly IRunnerSelector _runnerSelector;
        readonly IConsole _console;
        
        //---------------------------------------------------------
        public CommandLineParser(IRunnerSelector runnerSelector, IConsole console)
        {
            _runnerSelector = runnerSelector;
            _console = console;
        }

        //---------------------------------------------------------
        public CommandLineAutoBuildArguments Parse(string[] args)
        {
            var options = new CommandLineAutoBuildArguments();
            options.SetDefaultsFromAppConfig();
            var optionSet = new CommandLineOptionSet(options);
            var remaining = optionSet.Parse(args);

            if (!optionSet.Result.RunnerName.IsNullOrWhiteSpace())
            {
                var runner = _runnerSelector.GetRunner(optionSet.Result.RunnerName);
                if (runner != null)
                {
                    optionSet.Result.RunnerArgument = Activator.CreateInstance(runner.RunnerOptionsType);
                    AddRunnerOptions(runner, optionSet);
                    remaining = optionSet.Parse(args);
                }
            }
            
            if (args.Length == 0 || optionSet.Result.ShowHelp)
            {

                var message =
                    "{0}{1}{2}"
                        .With(
                            GetHelpDescription(optionSet),
                            Environment.NewLine,
                            GetAvailableRunnersDescription()
                        );
                _console.Info(message);
                throw new InvalidCommandLineException(message);
            }


            if (remaining.Count == 2)
            {
                optionSet.Result.WatchFolder = remaining[0];
                optionSet.Result.BuildFile = remaining[1];
            }
            else if (remaining.Count == 1 )
            {
                optionSet.Result.BuildFile = remaining[0];
            }
            else if (remaining.Count > 2)
            {

                var message =
                    "Additional parameters where passed: '{0}'. Please see {1} --help for reference"
                        .With(
                            string.Join(" ", remaining.ToArray()),
                            System.IO.Path.GetFileName(typeof (CommandLineParser).Assembly.Location));
                _console.Error(message);
                throw new InvalidCommandLineException(message);

            }
            return options;
        }

        void AddRunnerOptions(IBuildRunner runner, CommandLineOptionSet optionSet)
        {
            var interfaces = runner.GetType().GetInterfaces();
            var genericBuildRunnerType = interfaces.FirstOrDefault(i =>
            {
                if (i.IsGenericType && i.Name.Contains(typeof (IBuildRunner).Name))
                {
                    return true;
                }
                return false;
            });
            if (genericBuildRunnerType != null)
            {
                var typeArguments = genericBuildRunnerType.GetGenericArguments();
                var optionType = typeArguments[0];
                var optionObject = optionSet.Result.RunnerArgument;

                foreach (var property in optionType.GetProperties())
                {
                    var attributes = property.GetCustomAttributes(typeof (CommandLineOptionAttribute), true);
                    var optionAttribute = attributes.FirstOrDefault();
                    var innerProperty = property;
                    if ( optionAttribute != null)
                    {
                        var attribute = optionAttribute as CommandLineOptionAttribute;
                        optionSet.Add(attribute.Name, attribute.Description, v =>
                        {
                            
                            innerProperty.SetValue(optionObject, v, null);
                        });
                    }
                }
            }

        }

        //---------------------------------------------------------
        private string GetHelpDescription(OptionSet p)
        {
            var builder = new StringBuilder();

            builder
                .AppendLine("Usage: ")
                .AppendLine("{0} [Options]+ [folder] <build-file>"
                                .With(Path.GetFileNameWithoutExtension(GetType().Assembly.CodeBase))
                );
            builder.AppendLine();
            builder.AppendLine("Options:");

            using (var writer = new StringWriter(builder))
            {
                p.WriteOptionDescriptions(writer);
            }
            return builder.ToString();
        }

        //---------------------------------------------------------
        private string GetAvailableRunnersDescription()
        {
            var builder = new StringBuilder();

            builder.AppendLine("Available runners:");
            foreach (var runner in _runnerSelector.GetAvailableRunners())
            {
                builder.AppendLine("  - {0}".With(runner.RunnerName));
            }
            builder.AppendLine("");

            return builder.ToString();

        }
        
    }
}